import PlayController from '@/utils/PlayController';
import RemoveList from '@/utils/RemoveList';
import MapUtils from '@/mota/utils/MapUtils';
import KeyUtils, { KeyInterface } from '@/mota/utils/KeyUtils';
import TipUtils from '@/mota/utils/TipUtils';
import HeroUtils from '@/mota/utils/HeroUtils/index';
import DrawUtils, { DrawTool } from '@/mota/utils/DrawlUtils';
import SpeedUtils from '@/mota/utils/SpeedUtils';
import BuildUtils from "@/mota/utils/BuildUtils/index"
import MessgeUtils from '@/mota/utils/MessageUtils';
import StoreUtils from '@/mota/utils/StoreUtils';
import { TargetEvent } from '@/mota/data/EventDataUtils';

export interface TriggerInterface<CHILDREN extends TriggerInterface<CHILDREN>> {
	Children?: MotaInterface[] | undefined;
	Trigger?: () => void;
};

export type MotaInterface = DrawTool<MotaInterface> & TriggerInterface<MotaInterface> & KeyInterface<MotaInterface>;

/** 魔塔游戏类 */
export default class Mota implements MotaInterface {

	/** 播放控制类 */
	public readonly player: PlayController;

	public readonly draw: DrawUtils;

	public readonly hero: HeroUtils;

	public readonly speedUtils: SpeedUtils;

	public readonly map: MapUtils;

	public readonly key: KeyUtils;

	public readonly message: MessgeUtils;

	public readonly build: BuildUtils;

	public readonly tip: TipUtils;

	public readonly store: StoreUtils;

	public readonly eventRemove: RemoveList = new RemoveList();

	public readonly Children: MotaInterface["Children"];

	public readonly TriggerList: Set<VoidFunction> = new Set<VoidFunction>();

	public ZIndex: number = 8;

	/**
	 * 构造函数
	 * @param el 画板元素
	 */
	public constructor(el: HTMLCanvasElement) {
		this.player = new PlayController(this);
		this.draw = new DrawUtils(this, el);
		this.key = new KeyUtils(this);
		this.tip = new TipUtils(this);
		this.message = new MessgeUtils(this);
		this.speedUtils = new SpeedUtils(this);
		this.hero = new HeroUtils(this);
		this.map = new MapUtils(this);
		this.build = new BuildUtils(this);
		this.store = new StoreUtils(this);
		this.Children = [
			this.map,
			this.hero,
			this.message,
			this.tip,
			this.build,
			this.speedUtils,
			this.store,
		];
		this.player.Run();
	}

	public get onBuild() {
		return this.build.isOpen;
	}

	public Destroyed() {
		this.eventRemove.Remove();
	}

	public AddTrigger(trigger: VoidFunction) {
		this.TriggerList.add(trigger);
		return () => {
			this.TriggerList.delete(trigger);
		};
	}

	public async Update(): Promise<void> {
		const { draw, key } = this;

		key.JudgeKeyAll();
		draw.DrawAll();

		this.ForChildren((utils) => {
			if (utils.Trigger) {
				utils.Trigger();
			}
		});

		this.TriggerList.forEach((trigger) => {
			trigger();
		});
	}

	public JudgeKey: MotaInterface["JudgeKey"] = (keyUtils): void => {
		const { onBuild, build } = this;
		if (onBuild) { return; }

		for (const key of keyUtils.keyHistory) {
			if (key === "b" && keyUtils.has("Control")) {
				build.Open();
				keyUtils.delete(key);
			}
		}
	}

	public async JudgeHeroMove(tx: number, ty: number): Promise<boolean> {
		const { map, tip, hero, store } = this;
		const floor = map.floor;
		const unit = map.GetShowUnit(tx, ty);
		if (unit === undefined) { return true; }

		const [image, show, , state, event] = unit;
		if (typeof state === "number") { return false; }
		if (!show || event === undefined || event.size === 0) { return true; }

		const emitEvent = async () => {
			if (event.has("event")) {
				await TargetEvent(this.map.floor, tx, ty, unit, this);
			}
		};

		if (event.has("stop")) { return false; }
		if (event.has("seal")) { tip.AddIconMessage(image, "被一种未知力量所封印"); return false; }

		if (event.has("battle")) {
			if (!this.hero.Attribute.Battle(image)) {
				return false;
			} else {
				map.ChangeUnitShow(tx, ty, image, false);
				await emitEvent();
				return true;
			}
		}
		else if (event.has("consume")) {
			if (state === "first" && hero.UseItemTo(image)) {
				await map.UnitLeave(tx, ty, image);
				await emitEvent();
				return true;
			} else {
				return false;
			}
		}
		else if (event.has("get")) {
			if (hero.GetItem(image)) {
				map.ChangeUnitShow(tx, ty, image, false);
				await emitEvent();
				return true;
			} else {
				return false;
			}
		}
		else if (event.has("remove")) {
			await emitEvent();
			await map.UnitLeave(tx, ty, image);
			return false;
		}
		else if (event.has("up")) {
			await emitEvent();
			map.EnterMap(floor + 1);
			return false;
		}
		else if (event.has("down")) {
			await emitEvent();
			map.EnterMap(floor - 1);
			return false;
		}
		else if (event.has("store")) {
			await emitEvent();
			await store.Open(image);
			return false;
		}

		if (event.has("event")) { await TargetEvent(this.map.floor, tx, ty, unit, this); return false; }

		return true;
	}

	public Draw: MotaInterface["Draw"] = (draw) => {
		const { _2d } = draw;
		const { onBuild } = this;

		_2d.save();

		draw.SetLineWidth(1 / 12);
		_2d.strokeStyle = "#FFFFFF";
		draw.strokeRect(0, 0, 15, 11);
		draw.strokeRect(0, 0, 4, 11);
		if (!onBuild) {
			draw.strokeRect(0, 9, 4, 2);
		}

		_2d.restore();
	}

	public _ForChildren(info: MotaInterface | undefined, callback: (info: MotaInterface) => void) {
		if (info !== undefined) {
			callback(info);
			if (info.Children !== undefined) {
				for (const child of info.Children) {
					this._ForChildren(child, callback);
				}
			}
		}
	}

	public ForChildren(callback: (info: MotaInterface) => void) {
		this._ForChildren(this, callback);
	}

	public GetChildren() {
		const save = new Set<MotaInterface>();
		this.ForChildren((info) => {
			save.add(info);
		});
		return Array.from(save);
	}

}
